/**
 * 
 */
package com.feib.stms.service.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.feib.stms.crypto.DES;
import com.feib.stms.dao.SecuritiesDao;
import com.feib.stms.dao.TempPaymentDao;
import com.feib.stms.dao.UploadFileActionLogDao;
import com.feib.stms.dao.UploadFileDao;
import com.feib.stms.dao.UserDao;
import com.feib.stms.model.BaseTempPayment;
import com.feib.stms.model.MailQueue;
import com.feib.stms.model.Securities;
import com.feib.stms.model.TempUploadFile;
import com.feib.stms.model.TempUploadFileError;
import com.feib.stms.model.UploadFile;
import com.feib.stms.model.UploadFileActionLog;
import com.feib.stms.model.UploadFileTodoList;
import com.feib.stms.model.User;
import com.feib.stms.pagination.PaginatedList;
import com.feib.stms.service.DeliveryPaymentChkSumService;
import com.feib.stms.service.DeliveryPaymentSumService;
import com.feib.stms.service.FileImportorService;
import com.feib.stms.service.MailJobService;
import com.feib.stms.service.TempPaymentService;
import com.feib.stms.service.TempUploadFileErrorService;
import com.feib.stms.service.TempUploadFileService;
import com.feib.stms.service.UploadFileTodoListService;
import com.feib.stms.util.MailContentTemplate;
import com.feib.stms.util.StmsUtil;
import com.feib.stms.util.UnicodeInputStream;
import com.feib.stms.util.UserUtil;

/**
 * @author Jimmy Liu
 *
 */
public abstract class BaseFileImportorServiceImpl <K extends TempPaymentDao<T>, T extends BaseTempPayment > extends BaseServiceImpl implements FileImportorService<K, T> {

	private static final long serialVersionUID = 5197394673982656436L;

	@Resource(name="userDao")
	protected UserDao userDao;

	@Resource(name="uploadFileDao")
	protected UploadFileDao uploadFileDao;

	@Resource(name="tempUploadFileService")
	protected TempUploadFileService tempUploadFileService;

	@Resource(name="tempUploadFileErrorService")
	protected TempUploadFileErrorService tempUploadFileErrorService;

	@Resource(name="deliveryPaymentChkSumService")
	protected DeliveryPaymentChkSumService deliveryPaymentChkSumService;

	@Resource(name="deliveryPaymentSumService")
	protected DeliveryPaymentSumService deliveryPaymentSumService;

	@Resource(name="mailJobService")
	private MailJobService mailJob;

	@Resource(name="securitiesDao")
	protected SecuritiesDao securitiesDao;

	@Resource(name="uploadFileTodoListService")
	protected UploadFileTodoListService uploadFileTodoListService;

	@Resource(name="messageSource")
	protected ResourceBundleMessageSource messageSource;

	@Resource(name="uploadFileActionLogDao")
	private UploadFileActionLogDao uploadFileActionLogDao;



	@Resource(name="htmlMailFolder")
	private String htmlMailFolder;

	@Resource(name="defaultMailFrom")
	private String defaultMailFrom;

	@Resource(name="defaultMailReturnReceiptTo")
	private String defaultMailReturnReceiptTo;




	/**
	 * 檔名檢查的處理類別，由建構子產生
	 */
	protected Map<String,List<String>> stockTypeMap;

	/**
	 * 檔案內容的處理類別，由建構子產生
	 */
	protected List<String> acceptDealTypes; 

	/**
	 * 明細Table的Class，由建構子產生
	 */
	protected Class<T> toSaveTempPaymentClazz;

	/**
	 * 明細資料
	 */
	protected List<T> toSaveTempPayments = new ArrayList<T>();

	/**
	 * 檢核錯誤原因
	 */
	protected List<TempUploadFileError> toSaveValidateErrors = new ArrayList<TempUploadFileError>();

	protected boolean passed = true; // 若呼叫 addValidateError method, 則設定為 false, 代表有檢核錯誤

	/**
	 * 上傳人員，需透過Setter由Action設定
	 */
	protected User uploadUser;

	/**
	 * 上傳檔案紀錄，需透過Setter由Action設定
	 */
	protected TempUploadFile tempUploadFile;

	/**
	 * 更新明細資料資料庫的Service，需透過Setter由Action設定 或由 繼承的子類別設定
	 */
	protected TempPaymentService<K,T> tempPaymentService;

	protected Locale locale = new Locale("zh", "TW");


	@SuppressWarnings("unchecked")
	public BaseFileImportorServiceImpl()
	{
		stockTypeMap = new HashMap<String, List<String>>();
		List<String> dealType = new ArrayList<String>();
		dealType.add("0");
		dealType.add("6");
		dealType.add("5");
		dealType.add("9");
		stockTypeMap.put("H", dealType);

		dealType = new ArrayList<String>();
		dealType.add("0");
		dealType.add("6");
		dealType.add("5");
		dealType.add("9");
		stockTypeMap.put("R", dealType);

		dealType = new ArrayList<String>();
		dealType.add("A");
		dealType.add("B");
		dealType.add("C");
		dealType.add("D");
		dealType.add("E");
		dealType.add("F");
		dealType.add("G");
		dealType.add("H");
		stockTypeMap.put("P", dealType);

		acceptDealTypes = new ArrayList<String>();
		acceptDealTypes.add("0"); // 0.交割股款  
		acceptDealTypes.add("6"); // 6.認購款
		acceptDealTypes.add("Q"); // Q.餘額查詢 
		acceptDealTypes.add("4"); // 4.郵寄費及工本費
		acceptDealTypes.add("5"); // 5.未中籤退還款 
		acceptDealTypes.add("9"); // 9.券商轉

		toSaveTempPaymentClazz = (Class<T>) ((java.lang.reflect.ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];  
	}


	/**
	 * 檔名檢查
	 * 上傳檔案命名規格: <strong>交割日期_券商代號_遠銀分行代號_股票類別_處理類別_檔案序號.txt</strong>ex: 20111205_218*_0023_H_0_01.txt<br/>
	 * 交割日期：<strong>西元年月日(YYYYMMDD)</strong>, ex:20111205<br/>
	 * 券商代號：<strong>4碼</strong>, ex: <br/>
	 * <ol>
	 * <li>218#.亞東總公司</li>
	 * <li>2180.亞東-台北</li>
	 * <li>2181.亞東-板橋</li>
	 * </ol>
	 * 遠銀分行代號：<strong>4碼</strong>, ex: 0023.台北襄陽分行
	 * 股票類別：
	 * <ul>
	 * <li>H.一般上市櫃</li>
	 * <li>R.興櫃</li>
	 * <li>P.調撥</li>
	 * </ul>
	 * 處理類別：
	 * <ul>
	 * <li>0.交割股款</li> 
	 * <li>6.申購款</li>
	 * <li>4.郵寄費及工本費</li>
	 * <li>5.未中籤退還款 (P.S.此檔案內容可能會包含”4.郵寄費及工本費”之資料)</li>
	 * <li>9.券商轉（折讓）</li>
	 * <br/>-------以下當股票類別為(P:調撥)時使用-------<br/>
	 * <li>A.一般交割款手續費調撥</li>
	 * <li>B.一般交割款調撥</li>
	 * <li>C.興櫃交割款手續費調撥</li>
	 * <li>D.興櫃交割款調撥</li>
	 * <li>E.申購款調撥</li>
	 * <li>F.申購退款調撥</li>
	 * <li>G.申購手續費調撥</li>
	 * <li>H.融資券款項調撥</li>
	 * </ul>
	 * 檔案序號：<strong>2碼</strong> (從01開始), ex:01, 02
	 * @param tempUploadFile 暫存檔物件，內含檔案名稱
	 * @param uploadUser 上傳檔案的使用者
	 * @param checkErros 檢查不通過的錯誤訊息
	 * @return
	 */
	public TempUploadFile checkFileNameConventions()
	{
		String fileName = tempUploadFile.getFileName();

		/* step 檢查檔名是否為空*/
		logger.debug("檢查檔名是否為空.");
		String errorMsg = null;
		if (StringUtils.isEmpty(fileName))
		{
			errorMsg = messageSource.getMessage("import.file.check.fileName.isEmpty", null, "檔案名稱為空白", locale);
			addValidateError(0, null, errorMsg, null);
		}
		else
		{
			/* step 檢查檔名長度是否為24碼*/
			logger.debug("檢查檔名長度是否為29碼.");
			if (29 != fileName.length())
			{
				errorMsg = messageSource.getMessage("import.file.check.fileName.length.not.match", new Object[]{fileName}, "檔案名稱["+fileName+"]長度錯誤", locale);
				addValidateError(0, null, errorMsg, null);
			}
			else
			{
				/* step 檢查檔名是否已 .txt結尾*/
				logger.debug("檢查檔名是否已 .txt結尾.");
				if (! fileName.toLowerCase().endsWith(".txt"))
				{
					errorMsg = messageSource.getMessage("import.file.check.fileName.invalid.extension", null, "副檔名錯誤", locale);
					addValidateError(0, null, errorMsg, null);
				}

				int length = 0;
				/* step 檢查檔名1-8(8)碼是否為日期格式*/
				String yyyyMMdd = fileName.substring(0, length = length+8);
				logger.debug("檢查檔名1-8(8)碼是否為日期格式. ({})", yyyyMMdd);
				DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
				dateFormat.setLenient(false);
				try {


					/*交割日期不可晚於系統日 */
					Date   deliveryDate  =dateFormat.parse(yyyyMMdd);	 				

					Calendar  cal1 = Calendar.getInstance();
					cal1.setTime(deliveryDate);
					cal1.set(Calendar.HOUR_OF_DAY, 0);
					cal1.set(Calendar.MINUTE, 0);
					cal1.set(Calendar.SECOND, 0);
					cal1.set(Calendar.MILLISECOND, 0);
					
					Calendar cal2 = Calendar.getInstance();
					cal2.setTime(new java.util.Date());
					cal2.set(Calendar.HOUR_OF_DAY, 0);
					cal2.set(Calendar.MINUTE, 0);
					cal2.set(Calendar.SECOND, 0);
					cal2.set(Calendar.MILLISECOND, 0);
					
					if  (cal1.getTime().before(cal2.getTime()))
					{
						errorMsg = messageSource.getMessage("import.file.check.fileName.deliveryDate.format.invalid", new Object[]{fileName,yyyyMMdd}, "檔案名稱["+fileName+"]的交割日期["+yyyyMMdd+"]不可小於系統日", locale);
						addValidateError(0, null, errorMsg,null);				
					}				
					tempUploadFile.setDeliveryDate(deliveryDate);
				} catch (Exception e) {
					errorMsg = messageSource.getMessage("import.file.check.fileName.invalid.deliveryDate", new Object[]{fileName, yyyyMMdd}, "檔案名稱["+fileName+"]的交割日期["+yyyyMMdd+"]錯誤", locale);
					addValidateError(0, null, errorMsg, e);
				}



				/* step 檢查檔名10-13(4)碼是否為合法券商代號*/
				length ++; // "_" 不需處理
				String securitiesNo = fileName.substring(length , length = length+4);
				tempUploadFile.setSecuritiesNo(securitiesNo);
				logger.debug("檢查檔名10-13(4)碼是否為合法券商代號.({})", securitiesNo);
				boolean fileNamePass = false;

				/* 以券商代號檢核代號是否存在資料庫中 */
				logger.debug("券商代號檢核代號是否存在資料庫中.");
				Securities securities = securitiesDao.getBySecuritiesNo(securitiesNo);
				fileNamePass = null != securities; // 資料庫無此代號
				if(! fileNamePass)
				{
					tempUploadFile.setSecuritiesNo(null); // 檢核有誤, 不需要存
					errorMsg = messageSource.getMessage("import.file.check.fileName.invalid.securitiesNo", new Object[]{fileName, securitiesNo}, "檔案名稱["+fileName+"]的券商代號["+securitiesNo+"]錯誤", locale);
					addValidateError(0, null, errorMsg, null);
				}

				/* step 檢查檔名10-13(4)碼是否為使用者可處理的券商代號*/
				fileNamePass = false;
				String dealSecuritiesNos = uploadUser.getDealSecuritiesNos(); /* 取得使用者的設定 */
				logger.debug("檢查檔名10-13(4)碼是否為使用者可處理的券商代號.({} in ({}))", securitiesNo, dealSecuritiesNos);
				if (StringUtils.isNotEmpty(dealSecuritiesNos))
				{
					String[] securitiesNos = dealSecuritiesNos.split(","); // 拆解

					for(String no : securitiesNos)
					{
						if (StringUtils.isNotEmpty(no))
						{
							if (securitiesNo.equalsIgnoreCase(no.trim()))
							{
								fileNamePass = true;
								break;
							}
						}
					}
				}

				if(! fileNamePass)
				{
					tempUploadFile.setSecuritiesNo(null); // 檢核有誤, 不需要存
					errorMsg = messageSource.getMessage("import.file.check.fileName.user.cant.deal.securitiesNo", new Object[]{uploadUser.getUserId(), securitiesNo}, "使用者["+uploadUser.getUserId()+"]無法處理券商代號為["+securitiesNo+"]的檔案", locale);
					addValidateError(0, null, errorMsg, null);
				}

				/* step 檢查檔名15-18(4)碼是否為券商(分公司)所屬的遠銀分行代號*/
				length ++; // "_" 不需處理
				String bankBranchNo = fileName.substring(length , length = length+4);
				logger.debug("檢查檔名15-18(4)碼是否為券商(分公司)所屬的遠銀分行代號 .({})", bankBranchNo);
				tempUploadFile.setBankBracnchNo(bankBranchNo);

				/* 2012.05.18 專案會議中，已確認不需檢核
				// 以券商代號與遠銀分行代號是否存在資料庫中
				if(! pass)
				{
					uploadFile.setBankBracnchNo(null); // 檢核有誤, 不需要存
					checkErrors.add(createSimpleError(fileName,"import.file.check.fileName.securitiesNo.bankBranchNo.not.match", bankBranchNo, securitiesNo));
				}
				 */

				/* step 檢查檔名20-20(1)碼是否為合法的股票類別: H R P*/
				length ++; // "_" 不需處理
				String stockType = fileName.substring(length , length = length+1);
				logger.debug("檢查檔名20-20(1)碼是否為合法的股票類別.({})", stockType);
				tempUploadFile.setStockType(stockType);
				List<String> delTypes = stockTypeMap.get(stockType);
				fileNamePass = null != delTypes;
				if (!fileNamePass)
				{
					tempUploadFile.setStockType(null); // 檢核有誤, 不需要存
					errorMsg = messageSource.getMessage("import.file.check.fileName.invalid.stockType", new Object[]{fileName, stockType}, "檔案名稱["+fileName+"]的股票類別["+stockType+"]錯誤", locale);
					addValidateError(0, null, errorMsg, null);
				}

				/* step 檢查檔名22-22(1)碼是否為合法的處理類別*/
				length ++; // "_" 不需處理
				String dealType = fileName.substring(length , length = length+1);
				if (fileNamePass) // 若 股票類別 檢核通過，才需要檢核此欄位
				{
					tempUploadFile.setDealType(dealType);

					if ( ! delTypes.contains(dealType))
					{
						tempUploadFile.setDealType(null); // 檢核有誤, 不需要存
						errorMsg = messageSource.getMessage("import.file.check.fileName.invalid.dealType", new Object[]{fileName, dealType}, "檔案名稱["+fileName+"]的處理類別["+dealType+"]錯誤", locale);
						addValidateError(0, null, errorMsg, null);
					}
				}


				/* step 檢查檔名24-25(2)碼是否為合法的檔案序號: 01 ~ 99 */
				length ++; // "_" 不需處理
				String fileSn = fileName.substring(length , length = length+2);
				logger.debug("檢查檔名24-25(2)碼是否為合法的檔案序號.({})", fileSn);
				tempUploadFile.setFileSn(fileSn);
				try {
					Integer.parseInt(fileSn);
				} catch (Exception e) {
					tempUploadFile.setFileSn(null);// 檢核有誤, 不需要存
					errorMsg = messageSource.getMessage("import.file.check.fileName.invalid.fileSn", new Object[]{fileName, dealType}, "檔案名稱["+fileName+"]的序號["+fileSn+"]錯誤", locale);
					addValidateError(0, null, errorMsg, null);
				}

				/* step 檢查檔名是否已存在 FileUplad 裡面(排除取消/刪除/退件的資料) */
				/* 以檔名前22碼檢查不可重複，即序號以前的檔名不可重複*/
				logger.debug("檢查檔名是否已存在 FileUplad 裡面");
				List<UploadFile> files = this.uploadFileDao.queryValidateFileByFileName(fileName.substring(0, 22));
				fileNamePass = CollectionUtils.isEmpty(files);
				if(! fileNamePass)
				{
					errorMsg = messageSource.getMessage("import.file.check.fileName.duplicate", new Object[]{fileName, dealType}, "檔案名稱["+fileName+"]重複", locale);
					addValidateError(0, null, errorMsg, null);
				}

				/* step 檢查檔名是否已存在 TempFileUplad 裡面(排除檢核錯誤的資料) */
				/* 以檔名前22碼檢查不可重複，即序號以前的檔名不可重複*/
				logger.debug("檢查檔名是否已存在 TempFileUplad 裡面");
				List<TempUploadFile> tempFiles = this.tempUploadFileService.findValidateFileByFileName(fileName.substring(0, 22), tempUploadFile.getTempUploadFilePk());
				fileNamePass = CollectionUtils.isEmpty(tempFiles);
				if(! fileNamePass)
				{
					errorMsg = messageSource.getMessage("import.file.check.fileName.duplicate", new Object[]{fileName, dealType}, "檔案名稱["+fileName+"]重複", locale);
					addValidateError(0, null, errorMsg, null);
				}
			}

		}

		flushValidateErrors();

		if (! this.passed)
		{
			tempUploadFile.setFileStatus(TempUploadFile.FILE_STATUS_FILE_NAME_INVALID); // 設定檔名錯誤
		}

		return tempUploadFile;
	}

	public TempUploadFile checkFileContent()
	{
		String desKey = tempUploadFile.getDesKey();

		DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
		try {

			File sourceFile = new File(tempUploadFile.getFilePathOnServer());
			String charsetName = "MS950"; // or NULL to use systemdefaul
			BufferedReader reader = null;

			/* 讀取檔案 */
			try {
				FileInputStream fis = new FileInputStream(sourceFile);
				UnicodeInputStream uin = new UnicodeInputStream(fis, charsetName);
				charsetName = uin.getEncoding(); // check and skip possible BOM bytes

				InputStreamReader isr;
				if (null == charsetName) {

					isr = new InputStreamReader(uin);
				}
				else {
					isr = new InputStreamReader(uin, charsetName);
				}

				reader = new BufferedReader(isr);
			}
			catch (FileNotFoundException e) {
				throw new RuntimeException("檔案位置錯誤(" + sourceFile.getAbsolutePath() + ")", e);
			}
			catch (UnsupportedEncodingException e) {
				throw new RuntimeException("系統不支援此編碼:" + charsetName, e);
			}

			/* 讀取檔案 */
			logger.info("使用編碼{}拆解字串 ", charsetName);

			int lineLength = 100; // 總長度 100

			String lineData = null;
			byte[] lineBytes = null;
			int lineNumber = 0; // 行號
			String errorMsg = null;
			T payment = null;

			// 逐行檢查
			while ((lineData = reader.readLine()) != null) {
				try {

					lineNumber++;
					// 解密
					if (StringUtils.isNotEmpty(desKey))
					{
						lineData = DES.decryptData(desKey, lineData);
					}

					lineBytes = lineData.getBytes(charsetName);
					//lineBytes = lineData.getBytes();

					// step1 : 檢查長度，若長度不對，不用再往下檢查
					if (lineLength != lineBytes.length) {
						// 長度不不符
						errorMsg = messageSource.getMessage("import.file.check.content.length.not.match",  new Object[]{lineLength, lineBytes.length}, "長度不符，應為[" + lineLength + "]，但實際為[" + lineBytes.length + "]", locale);
						addValidateError(lineNumber, null, errorMsg, null);

						// 此筆資料不需再做其他檢查，跳到下一筆資料
						continue;
					}

					// step2 : 建立一筆新的繳費單，若檢核都通過，才可以新增到資料庫中
					payment = toSaveTempPaymentClazz.newInstance();

					int copyIndex = 0; // 起始位置
					int copyLength = 14; // 長度

					// 存款帳號, 999-999-9999999-9 分行別-科目別-序號-檢碼
					String accountNo = copyString(lineBytes, copyIndex, copyLength, charsetName);
					try {
						Long.parseLong(accountNo);

						if (14 != accountNo.length())
						{
							errorMsg = messageSource.getMessage("import.file.check.content.accountNo.length.not.match", new Object[]{14, accountNo.length()}, "長度不符，應為[14]，但實際為[" + accountNo.length() + "]", locale);
							addValidateError(lineNumber, "存款帳號", errorMsg, null);
						}
						else
							payment.setAccountNo(accountNo);
					} catch (NumberFormatException e) {
						errorMsg = messageSource.getMessage("import.file.check.content.accountNo.format.invalid", null, "格式錯誤，應皆為數字", locale);
						addValidateError(lineNumber, "存款帳號", errorMsg, e);
					}



					// 交割日期, 國曆形式：YYMMDD，交割日期或扣帳日期    EX:100/01/02==>000102
					copyIndex = copyIndex + copyLength;
					copyLength = 6;
					String deliveryDateStr = copyString(lineBytes, copyIndex, copyLength, charsetName);
					try {
						int y = 19110000 ;

						y = y + Integer.parseInt("1" +deliveryDateStr); //轉成西元年數字

						/* 檔案交割日期不可晚於系統日  */
						Date   deliveryDate  =yyyyMMdd.parse(String.valueOf(y));					

						Calendar  cal1 = Calendar.getInstance();
						cal1.setTime(deliveryDate);
						cal1.set(Calendar.HOUR_OF_DAY, 0);
						cal1.set(Calendar.MINUTE, 0);
						cal1.set(Calendar.SECOND, 0);
						cal1.set(Calendar.MILLISECOND, 0);
						
						Calendar cal2 = Calendar.getInstance();
						cal2.setTime(new java.util.Date());
						cal2.set(Calendar.HOUR_OF_DAY, 0);
						cal2.set(Calendar.MINUTE, 0);
						cal2.set(Calendar.SECOND, 0);
						cal2.set(Calendar.MILLISECOND, 0);
						
						if  (cal1.getTime().before(cal2.getTime()))
						{
							errorMsg = messageSource.getMessage("import.file.check.content.deliveryDate.format.invalid",   new Object[]{deliveryDateStr}, "交割日期"+deliveryDateStr+"不可小於系統日", locale);
							addValidateError(lineNumber, "交割日期", errorMsg, null);
						}

						payment.setDeliveryDate(deliveryDate);

					} catch (Exception e) {
						errorMsg = messageSource.getMessage("import.file.check.content.deliveryDate.format.invalid", new Object[]{deliveryDateStr}, "格式錯誤，應為6碼民國年數字，但實際為["+deliveryDateStr+"]", locale);
						addValidateError(lineNumber, "交割日期", errorMsg, e);
					}







					// 淨付金額, 客戶賣出股票金額，右靠左補零，後兩位小數位
					copyIndex = copyIndex + copyLength;
					copyLength = 13;
					String netPayStr = copyString(lineBytes, copyIndex, copyLength, charsetName);
					try {
						if (null == netPayStr || 0 == Long.parseLong(netPayStr))
						{
							payment.setNetPay(new BigDecimal(0));
						}
						else
						{
							payment.setNetPay(new BigDecimal(netPayStr.substring(0,11)+"." + netPayStr.substring(11, 13)));
						}

					} catch (NumberFormatException e) {
						errorMsg = messageSource.getMessage("import.file.check.content.netPay.format.invalid",  new Object[]{13, netPayStr}, "格式錯誤，應為13碼數字，但實際為["+netPayStr.length()+"]", locale);
						addValidateError(lineNumber, "淨付金額", errorMsg, e);
					}


					// 淨收金額, 客戶買入股票金額，右靠左補零，後兩位小數位
					copyIndex = copyIndex + copyLength;
					copyLength = 13;
					String netIncomeStr = copyString(lineBytes, copyIndex, copyLength, charsetName);
					try {
						if (null == netIncomeStr || 0 == Long.parseLong(netIncomeStr))
						{
							payment.setNetIncome(new BigDecimal(0));
						}
						else
						{
							payment.setNetIncome(new BigDecimal(netIncomeStr.substring(0,11)+"." + netIncomeStr.substring(11, 13)));
						}

					} catch (Exception e) {
						errorMsg = messageSource.getMessage("import.file.check.content.netIncome.format.invalid",  new Object[]{13, netIncomeStr}, "格式錯誤，應為13碼數字，但實際為["+netIncomeStr.length()+"]", locale);
						addValidateError(lineNumber, "淨收金額", errorMsg, e);
					}

					if  ( (null == payment.getNetPay() || 0 == payment.getNetPay().doubleValue()) && (null == payment.getNetIncome() || 0 == payment.getNetIncome().doubleValue()))
					{
						errorMsg = messageSource.getMessage("import.file.check.content.netPay.netIncome.must.one",  null, "[淨付金額]與[淨收金額]不可皆為 0", locale);
						addValidateError(lineNumber, "淨付金額", errorMsg, null);
					}

					// 空白, 不處理
					copyIndex = copyIndex + copyLength;
					copyLength = 1;

					// 帳號檢查碼M
					copyIndex = copyIndex + copyLength;
					copyLength = 2;
					String accountNoChk = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setAccountNoChk(accountNoChk);
					/* 不檢查
					if (checkAccountNo(accountNo, accountNoChk))
					{
						payment.setAccountNoChk(accountNoChk);
					}
					else
					{
						errorMsg = messageSource.getMessage("import.file.check.content.accountNo.chk.invalid", new Object[]{accountNoChk}, "帳號檢查碼M錯誤，實際為[" + accountNoChk + "]", locale);
						addValidateError(lineNumber, "帳號檢查碼M", errorMsg, null);						
					}
					 */



					// 金額檢查碼N
					copyIndex = copyIndex + copyLength;
					copyLength = 2;
					String amountChk = copyString(lineBytes, copyIndex, copyLength, charsetName);

					payment.setAmountChk(amountChk);

					// 處理類別, 0.交割股款 6.認購款 Q.餘額查詢 4. 郵寄費及工本費 5. 未中籤退還款 9.券商轉
					copyIndex = copyIndex + copyLength;
					copyLength = 1;
					String dealType = copyString(lineBytes, copyIndex, copyLength, charsetName);
					if (acceptDealTypes.contains(dealType))
					{
						payment.setDealType(dealType);
					}
					else
					{
						acceptDealTypes.toString();
						errorMsg = messageSource.getMessage("import.file.check.content.dealType.invalid",  new Object[]{13, netIncomeStr}, "代碼錯誤，應為 "+acceptDealTypes+" 其中之一", locale);
						addValidateError(lineNumber, "處理類別", errorMsg, null);
					}

					// 空白, 不處理
					copyIndex = copyIndex + copyLength;
					copyLength = 1;

					//客戶戶名
					copyIndex = copyIndex + copyLength;
					copyLength = 10;
					String customerName = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setCustomerName(customerName);

					// 空白, 不處理
					copyIndex = copyIndex + copyLength;
					copyLength = 1;

					// 證券代號, 餘額查詢時，放申購委託書編號
					copyIndex = copyIndex + copyLength;
					copyLength = 6;
					String stockNo = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setStockNo(stockNo);

					// 空白, 不處理
					copyIndex = copyIndex + copyLength;
					copyLength = 1;

					// 證券名稱
					copyIndex = copyIndex + copyLength;
					copyLength = 8;
					String stockName = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setStockName(stockName);

					// 空白, 不處理
					copyIndex = copyIndex + copyLength;
					copyLength = 1;

					// 扣款優先種類
					copyIndex = copyIndex + copyLength;
					copyLength = 1;
					String chargebackPriority = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setChargebackPriority(chargebackPriority);

					// 扣款先後序號
					copyIndex = copyIndex + copyLength;
					copyLength = 4;
					String chargebackPriorityCode = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setChargebackPriorityCode(chargebackPriorityCode);

					// 營業櫃員
					copyIndex = copyIndex + copyLength;
					copyLength = 3;
					String teller = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setTeller(teller);

					// 處理狀況代號
					copyIndex = copyIndex + copyLength;
					copyLength = 2;
					String dealResultCode = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setDealResultCode(dealResultCode);

					// 身份證號
					copyIndex = copyIndex + copyLength;
					copyLength = 10;
					String customerId = copyString(lineBytes, copyIndex, copyLength, charsetName);
					payment.setCustomerId(customerId);


					// 檢查碼檢查
					payment.setAccountNoChksum(new Long(StmsUtil.getInstance().getAccountNoChecksum(accountNo)));
					payment.setNetIncomeChksum(new Long(StmsUtil.getInstance().getNetIncomeChecksum(netIncomeStr)));
					payment.setNetPayChksum(new Long(StmsUtil.getInstance().getNetPayChecksum(netPayStr)));
					payment.setCustomerIdChksum(new Long(StmsUtil.getInstance().getIdentifierChecksum(customerId)));
					String chksum = StringUtils.leftPad(String.valueOf(payment.getAccountNoChksum()), 2, "0") +
					StringUtils.leftPad(String.valueOf(payment.getNetIncomeChksum()), 2, "0") +
					StringUtils.leftPad(String.valueOf(payment.getNetPayChksum()), 2, "0") +
					StringUtils.leftPad(String.valueOf(payment.getCustomerIdChksum()), 2, "0") ;

					payment.setChksum(new Long(chksum));
					
					
					// 約定帳號
					payment.setPreDesignatedAccount(tempUploadFile.getPreDesignatedAccount());

				} catch (Exception e) {
					logger.error("第 "+lineNumber+" 行內容格式有誤，原始資料["+lineData+"]:", e);
					errorMsg = messageSource.getMessage("import.file.check.content.exception",  null, "檔案格式有誤，請聯絡系統管理員", locale);
					addValidateError(lineNumber, null, errorMsg, e);
				}

				if (this.passed)
				{
					addTempPayment(payment);
				}

			} // end of while ((lineData = reader.readLine()) != null);

		} catch (Exception e) {
			logger.error("檔案格式有誤:", e);
			String errorMsg = messageSource.getMessage("import.file.check.content.exception",  null, "檔案格式有誤，請聯絡系統管理員", locale);
			addValidateError(0, null, errorMsg, e);
			tempUploadFile.setFileStatus(TempUploadFile.FILE_STATUS_EXCEPTION); // 設定系統錯誤
		}
		finally
		{
			// 若不通過，需刪除明細檔
			if (! this.passed)
			{
				deleteTempPaymentByTempUploadFilePk();
			}
		}


		if (! this.passed)
		{
			flushValidateErrors();

			if (!TempUploadFile.FILE_STATUS_EXCEPTION.equals(tempUploadFile.getFileStatus()))
				tempUploadFile.setFileStatus(TempUploadFile.FILE_STATUS_CONTENT_INVALID); // 設定檔案內容錯誤
		}
		else
		{
			flushTempPayments();

			/* 驗資檔 與 交割檔  需再做彙總 */
			if (UploadFile.UPLOAD_FILE_DELIVERY_PAYMENT_CHK.equalsIgnoreCase(tempUploadFile.getFileFormat()) || UploadFile.UPLOAD_FILE_DELIVERY_PAYMENT.equalsIgnoreCase(tempUploadFile.getFileFormat()) )
			{
				tempPaymentService.summaryPaymentChk(tempUploadFile);
			}

			/* 計算總計 */
			tempUploadFile = tempPaymentService.getSummary(tempUploadFile);
			tempUploadFile.setFileStatus(TempUploadFile.FILE_STATUS_IMPORT_OK); // 設定檔案檢核通過
		}

		return tempUploadFile;
	}

	/**
	 * 帳號檢查碼(M)計算公式<br/>
	 * 存款帳號:
	 * <table BORDER="2">
	 * <tr><th>分行別</th><th>科目別</th><th>序號</th><th>檢碼</th></tr>
	 * <tr><td>N1&nbsp; N2&nbsp; N3</td><td>N4&nbsp; N5&nbsp; N6</td><td>N7&nbsp; N8&nbsp; N9&nbsp; N10&nbsp; N11&nbsp; N12&nbsp; N13</td><td>N14</td></tr>
	 * </table>
	 * 
	 * @param accountNo
	 * @param checkSum
	 * @return
	 */
	protected boolean checkAccountNo(String accountNo, String checkSum)
	{
		return String.valueOf(StmsUtil.getInstance().getAccountNoChecksum(accountNo)).equals(checkSum);		
	}

	/**
	 * 淨收金額檢查碼 N<br/>
	 * 交易金額
	 * <table BORDER="2">
	 * <tr><td>N1&nbsp; N2&nbsp; N3&nbsp; N4&nbsp; N5&nbsp; N6&nbsp; N7&nbsp; N8&nbsp; N9&nbsp; N10&nbsp; N11&nbsp; N12&nbsp; N13</td></tr>
	 * </table>
	 * 
	 * @param netIncome
	 * @return
	 */
	protected boolean checkNetIncome(String netIncome, String checkSum)
	{
		return String.valueOf(StmsUtil.getInstance().getNetIncomeChecksum(netIncome)).equals(checkSum);		
	}

	/**
	 * 淨付金額檢查碼 N<br/>
	 * 交易金額
	 * <table BORDER="2">
	 * <tr><td>N1&nbsp; N2&nbsp; N3&nbsp; N4&nbsp; N5&nbsp; N6&nbsp; N7&nbsp; N8&nbsp; N9&nbsp; N10&nbsp; N11&nbsp; N12&nbsp; N13</td></tr>
	 * </table>
	 * 
	 * @param netpay
	 * @return
	 */
	protected boolean checkNetPay(String netPay, String checkSum)
	{
		return String.valueOf(StmsUtil.getInstance().getNetPayChecksum(netPay)).equals(checkSum);		
	}


	protected String copyString(byte[] lineBytes, int index, int length, String charsetName) throws UnsupportedEncodingException
	{	
		String value = "";
		value = new String(lineBytes, index, length, charsetName);

		value = value.trim();

		if (0 == value.length())
			return null;
		else
			return value;
	}


	/**
	 * 加入一筆檢核錯誤原因，若累積超過50筆資料，則整批新增到資料庫中
	 * 
	 * @param lineNumber 第幾行錯誤
	 * @param columnName 錯誤欄位
	 * @param errorMessage 錯誤原因
	 * @param exceptionMessage 程式錯誤原因
	 */
	protected void addValidateError(int lineNumber, String columnName, String errorMessage, Exception exceptionMessage) {
		passed = false; // 不需要再新增明細，並且需要刪除已新增進去的資料(程式結束時刪即可)

		TempUploadFileError error = new TempUploadFileError();
		error.setTempUploadFile(tempUploadFile);
		error.setLineNumber(lineNumber);
		error.setColumnName(columnName);
		error.setErrorMessage(errorMessage);
		if (null != exceptionMessage)
			error.setExceptionMessage(exceptionMessage.getMessage());

		logger.debug("addValidateError:lineNumber=[{}],\r\n columnName=[{}],\r\n errorMessage=[{}],\r\n exceptionMessage=[{}]", new Object[]{lineNumber, columnName, errorMessage, exceptionMessage});

		toSaveValidateErrors.add(error);

		if (toSaveValidateErrors.size() >= 100) {
			// 將錯誤資料新增到資料庫中
			flushValidateErrors();
		}
	}

	protected void addTempPayment(T payment) 
	{		
		payment.setTempUploadFile(tempUploadFile);
		payment.setGroup(tempUploadFile.getGroup());
		payment.setSecuritiesNo(tempUploadFile.getSecuritiesNo());
		
		payment.setPreDesignatedAccount(tempUploadFile.getPreDesignatedAccount()); // 約定帳號

		this.toSaveTempPayments.add(payment);

		if (toSaveTempPayments.size() >= 100) {
			// 將繳費資料新增到資料庫中
			flushTempPayments();
		}
	}

	@Transactional(readOnly = false, propagation=Propagation.REQUIRES_NEW)
	protected void flushTempPayments() {
		if (passed && CollectionUtils.isNotEmpty(toSaveTempPayments))
		{
			tempPaymentService.addBaseTempPayments(toSaveTempPayments);
		}
		// 清除memory資料
		this.toSaveTempPayments.clear();
	}

	public void deleteTempPaymentByTempUploadFilePk()
	{
		tempPaymentService.deleteByTempUploadFilePk(this.tempUploadFile.getTempUploadFilePk());
	}

	public void setTempPaymentService(TempPaymentService<K, T> tempPaymentService) {
		this.tempPaymentService = tempPaymentService;
	}



	@Transactional(readOnly = false, propagation=Propagation.REQUIRES_NEW)
	protected void flushValidateErrors() {
		if (CollectionUtils.isNotEmpty(toSaveValidateErrors))
		{
			tempUploadFileErrorService.addTempUploadFileErrors(this.toSaveValidateErrors);
		}
		// 清除memory資料
		this.toSaveValidateErrors.clear();
	}

	public int deleteByTempUploadFilePk(Long tempUploadFilePk)
	{
		tempUploadFileErrorService.deleteByTempUploadFilePk(tempUploadFilePk);
		deliveryPaymentSumService.deleteByTempUploadFilePk(tempUploadFilePk); // 刪除交割彙總檔
		tempPaymentService.deleteByTempUploadFilePk(tempUploadFilePk);
		deliveryPaymentChkSumService.deleteByTempUploadFilePk(tempUploadFilePk); // 刪除驗資彙總檔
		return tempUploadFileService.deleteByPk(tempUploadFilePk);	
	}

	public void moveToUploadFile(Long tempUploadFilePk, Integer checkCount, Integer confirmCount, String preDesignatedAccount)
	{
		this.moveToUploadFile(tempUploadFilePk, checkCount, confirmCount, false, preDesignatedAccount);
	}

	public void moveToUploadFile(Long tempUploadFilePk, Integer checkCount, Integer confirmCount, boolean oneToTwo, String preDesignatedAccount)
	{
		/* 複製到主檔與明細檔 */
		UploadFile uploadFile = null;
		if (oneToTwo)
		{
			/* 驗資轉交割  */
			uploadFile = this.uploadFileDao.copyFromFormat1UploadFilePk(tempUploadFilePk, checkCount, confirmCount, preDesignatedAccount);
		}
		else
		{   /* 單純上傳 */
			uploadFile = this.uploadFileDao.copyFromTempUploadFilePk(tempUploadFilePk, checkCount, confirmCount, preDesignatedAccount);
		}


		/* 新增待辦事項 */
		this.uploadFileTodoListService.addUploadFileTodoList(uploadFile);


		/* 新增檔案操作紀錄  */
		UploadFileActionLog uploadFileActionLog=new UploadFileActionLog();
		uploadFileActionLog.setUploadFile(uploadFile);
		uploadFileActionLog.setAction(UploadFileActionLog.UPLOAD_FILE_ACTION_LOG_UPLOAD);
		uploadFileActionLog.setActionTime(new Date());
		uploadFileActionLog.setUser(uploadFile.getUploadUser());		
		uploadFileActionLogDao.insert(uploadFileActionLog);

		/* 刪除暫存檔 */
		this.deleteByTempUploadFilePk(tempUploadFilePk);


		//設定查詢的目錄權限
		Long menuItemPk=new Long(0);

		if (UploadFile.UPLOAD_FILE_DELIVERY_PAYMENT.equals(uploadFile.getFileFormat()))
		{
			menuItemPk=UploadFileTodoList.TODO_CHECK_DELIVERY_PAYMENT;
		}

		else if (UploadFile.UPLOAD_FILE_PURCHASE_PAYMENT.equals(uploadFile.getFileFormat()))
		{
			menuItemPk=UploadFileTodoList.TODO_CHECK_PURCHASE_PAYMENT;
		}
		else if (UploadFile.UPLOAD_FILE_PURCHASE_RETURN_PAYMENT.equals(uploadFile.getFileFormat()))
		{
			menuItemPk=UploadFileTodoList.TODO_CHECK_PURCHASE_RETURN_PAYMENT;
		}else if (UploadFile.UPLOAD_FILE_TRANSFER.equals(uploadFile.getFileFormat()))
		{
			menuItemPk=UploadFileTodoList.TODO_CHECK_TRANSFER;
		}
		
		/* 驗資上傳不發mail */
		if (!UploadFile.UPLOAD_FILE_DELIVERY_PAYMENT_CHK.equals(uploadFile.getFileFormat()))
		{		

			/* 依檔案 個別發送mail */				
			List<String> emails = userDao.findUserEmailByGroupMenuId(UserUtil.getUserGroup(),menuItemPk, tempUploadFilePk);
			if (null != emails && 0 != emails.size())
			{

				StringBuffer mailTo = new StringBuffer();
				for(String email : emails)
					mailTo.append(email).append(",");
				logger.debug("發送mail {}",mailTo);
				SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

				String messageTile = " 您有 審查作業 待執行";
				StringBuffer paragraph1 = new StringBuffer();
				paragraph1.append(UserUtil.getUser().getUserName()).append(" 於 ").append(dateFormat.format(new Date()));
				paragraph1.append(" 上傳 ").append(uploadFile.getFileName()).append("， 待您確認審查。");
				paragraph1.append("<br/>請您至系統確認資料內容。");
				String[] paragraphs = new String[]{"您好", paragraph1.toString(), "謝謝"};

				MailContentTemplate mailContentTemplate = new MailContentTemplate(messageTile, paragraphs, htmlMailFolder);
				MailQueue mailQueue;
				try {
					mailQueue = mailContentTemplate.createMailQueue();

					mailQueue.setMailTo(mailTo.toString());
					mailQueue.setMailFrom(defaultMailFrom);
					mailQueue.setMailReturnReceiptTo(defaultMailReturnReceiptTo);
					mailQueue.setMailStatus(0);
					mailJob.createAndSend(mailQueue);
					mailQueue = null; // free resource
				}catch (Exception e) {
					logger.error("", e);
				}

			}
		}

	}

	public PaginatedList<T> findPaginatedListByTempUploadFilePk(Long pk, PaginatedList<T> paginatedList)
	{
		return tempPaymentService.findPaginatedListByTempUploadFilePk(pk, paginatedList);
	}
	
	public PaginatedList<T> findChkPaginatedListByUploadFilePk(Long pk, PaginatedList<T> paginatedList)
	{
		return tempPaymentService.findChkPaginatedListByUploadFilePk(pk, paginatedList);
	}

	public TempUploadFile getTempUploadFile() {
		return tempUploadFile;
	}

	public void setTempUploadFile(TempUploadFile tempUploadFile) {
		this.tempUploadFile = tempUploadFile;
	}

	public boolean isPassed() {
		return passed;
	}

	public void setUploadUser(User uploadUser) {
		this.uploadUser = uploadUser;
	}

}

